package me.tonywang.config;

import me.tonywang.security.access.ProtectedUrlFilter;
import me.tonywang.security.access.ProtectedUrlSecurityMetadataSource;
import me.tonywang.security.authentication.UserSignAuthenticationFilter;
import me.tonywang.security.authentication.UserSignAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Map;

@Configuration
// http://docs.spring.io/spring-boot/docs/current/reference/html/howto-security.html
// Switch off the Spring Boot security configuration
//@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    // roles admin allow to access /admin/**
    // roles user allow to access /user/**
    // custom 403 access denied handler
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // fix X-Frame-Options:DENY
        http.headers().frameOptions().disable();

        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/", "/home", "/about", UserSignAuthenticationFilter.USER_SIGN_LOGIN_URL).permitAll()
//                .antMatchers("/admin/**").hasAnyRole("ADMIN")
//                .antMatchers("/user/**").hasAnyRole("USER")
//                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
                .logout()
                .permitAll()
                .and()
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler);

        //自定义身份认证方式,
        http.addFilterBefore(buildUserSignAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

        //自定义授权方式
        http.addFilterBefore(buildProtectedUrlFilter(), FilterSecurityInterceptor.class);

    }


    // PasswordEncoderFactories
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
      /*  auth.inMemoryAuthentication()
                .withUser("user").password("{noop}password").roles("USER")
                .and()
                .withUser("admin").password("{noop}password").roles("ADMIN")
                .and()
                .withUser("test").password("{noop}password").roles("ADMIN");*/

        auth.inMemoryAuthentication().passwordEncoder(NoOpPasswordEncoder.getInstance())
                .withUser("user").password("password").roles("USER")
                .and()
                .withUser("admin").password("password").roles("ADMIN")
                .and()
                .withUser("test").password("password").roles("ADMIN");

        auth.authenticationProvider(new UserSignAuthenticationProvider());
    }

    @Value("${security.ignored:/css/**, /js/**,/images/**, /webjars/**, /**/favicon.ico,/Hplus4.1/**,/assets/**}")
    String[] antPatterns;

    //Spring Boot configured this already.
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers(antPatterns);
//        web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }


    UserSignAuthenticationFilter buildUserSignAuthenticationFilter() throws Exception {
        UserSignAuthenticationFilter filter = new UserSignAuthenticationFilter();
        AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler("/login?signerror=true");
        filter.setAuthenticationManager(authenticationManager());
        filter.setAuthenticationFailureHandler(failureHandler);
        filter.setAuthenticationSuccessHandler(successHandler);
        return filter;
    }


    @Bean
    ProtectedUrlSecurityMetadataSource getSecurityMetadataSource() {
//        Map<String, String> urlMap = new HashMap<>();
//        urlMap.put("/admin/**", "hasAnyRole('ADMIN')");
//        urlMap.put("/user/**", "hasAnyRole('USER')");
//        return new ProtectedUrlSecurityMetadataSource(urlMap);
        return new ProtectedUrlSecurityMetadataSource();
    }

    //WebSecurityConfiguration
    @Autowired
    ApplicationContext beanFactory;

    Filter buildProtectedUrlFilter() {
        DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
        expressionHandler.setApplicationContext(beanFactory);
        ProtectedUrlFilter filter = new ProtectedUrlFilter(expressionHandler);
        filter.setSecurityMetadataSource(getSecurityMetadataSource());
        return filter;
    }

    @Bean
    MappedInterceptor interceptor1() {

        HandlerInterceptor interceptor = new HandlerInterceptor() {
            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
                System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            }

            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
                System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            }
        };
        return new MappedInterceptor(new String[]{"/*"}, interceptor);
    }

    //@see EnableWebMvcConfiguration
    @Bean
    WebMvcRegistrations webMvcRegistrations() {
        return new WebMvcRegistrations() {
            @Override
            public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
                return new RequestMappingHandlerMapping() {
                    // ========
                    @Override
                    protected void handlerMethodsInitialized(Map<RequestMappingInfo, HandlerMethod> handlerMethods) {
                        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
                            logger.debug("path : " + entry.getKey().getPatternsCondition().getPatterns());
                            logger.debug("RequestMappingInfo : " + entry.getKey().getName());
                            logger.debug("HandlerMethod : " + entry.getValue().getMethod());
//                            if(entry.getKey().getName()==null){
//                                throw new IllegalArgumentException(entry.getValue().getMethod() + "RequestMappingInfo name is null ");
//                            }
                        }
                    }

                    //@see  AbstractHandlerMethodMapping.initHandlerMethods---> getMappingForMethod --> createRequestMappingInfo
                    @Nullable
                    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
                        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(handlerType, RequestMapping.class);
//                        if (requestMapping != null)
//                            logger.debug("handlerType path : " + Arrays.asList(requestMapping.name()));
                        return null;
                    }

                    @Nullable
                    protected RequestCondition<?> getCustomMethodCondition(Method method) {
                        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
//                        if (requestMapping != null)
//                            logger.debug("method path : " + Arrays.asList(requestMapping.path()));
                        return null;
                    }

                    @Override
                    protected boolean isHandler(Class<?> beanType) {
                        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
                    }
                };
            }
        };
    }

}
